<?php

//
// Edit History:
//
//  Last $Author: munroe $
//  Last Modified: $Date: 2006/09/26 14:39:46 $
//
//  Dick Munroe (munroe@csworks.com) 28-Feb-2006
//      Initial version created
//
//  Dick Munroe (munroe@csworks.com) 24-Mar-2006
//      Modify getData interface to allow access to the entire contents of
//      the m_IPNData member.  Used by the PDT processing to long the
//      retrieved data.
//

/**
 * @author Dick Munroe <munroe@csworks.com>
 * @copyright copyright @ 2006 by Dick Munroe, Cottage Software Works, Inc.
 * @license http://www.csworks.com/publications/ModifiedNetBSD.html
 * @version 1.0.0
 * @package dm.paypal
 * @example ./example.php
 *
 * This is derived from a paypal IPN class written originally by Herve Foucher
 * <Herve.Foucher@helio.org> and published through phpclasses.org under the GPL.
 * Herve appears to no longer support this package so I'm updating the support
 * to 2005 and redesigning a bunch of the internal structure to allow a substantially
 * more object oriented approach to the whole problem.
 */

include_once('class.paypalIPNAdvancedAndCustomInformation.php') ;
include_once('class.paypalIPNAuctions.php') ;
include_once('class.paypalIPNBasicInformation.php') ;
include_once('class.paypalIPNBuyerInformation.php') ;
include_once('class.paypalIPNCurrencyAndExchangeInformation.php') ;
include_once('class.paypalIPNDisputeNotification.php') ;
include_once('class.paypalIPNMassPayment.php') ;
include_once('class.paypalIPNPDTSpecific.php') ;
include_once('class.paypalIPNSubscriptions.php') ;
include_once('class.paypalIPNWebsiteAndRefundInformation.php') ;

class paypalIPNData
{
    /**
     * @desc The collection of IPN data values.
     * @var array Associative array relating field names to values.
     * @access private
     */

    var $m_IPNData ;

    /**
     * @desc The ORDER of items as they appear in the IPN data source.
     *
     * Apparently yet another undocumented problem with Paypal is that unless you send
     * the IPN fields back in the same ORDER as well as with the same DATA you appear
     * to get invalid responses across the board.
     *
     * @var array The field names, in the order in which they apper in the source.
     * @access private
     */

    var $m_IPNDataOrder ;

    /**
     * Constructor.
     *
     * Pull the arguments passed in the source array into the
     * IPN data object.
     *
     * @param        array        $theSource [by reference] The source array containing the variables to
     *                         be pulled into the IPN data object.
     * @param        boolean        $theReducingFlag [optional] When true, the elements moved into the
     *                         IPN data object are removed from the $_POST array.
     * @return  void
     * @access  public
     */

    function paypalIPNData (&$theSource, $anyAdditionalFields = NULL, $theReducingFlag = TRUE)
    {
        $this->m_IPNData = array() ;

        $this->m_IPNDataOrder = array_keys($theSource) ;

        $theObjects = array() ;

        if ($theReducingFlag)
        {
            $theLocalSource =& $theSource ;
        }
        else
        {
            $theLocalSource = $theSource ;
        }

        /*
         * Gather all the Paypal IPN into their seperate objects.
         * The "bunches" of small IPN specific objects are just to make
         * it easier to figure out what variables go with what applications.
         */

        $theObjects[] = new paypalIPNBasicInformation($theLocalSource) ;
        $theObjects[] = new paypalIPNAdvancedAndCustomInformation($theLocalSource) ;
        $theObjects[] = new paypalIPNAuctions($theLocalSource) ;
        $theObjects[] = new paypalIPNBuyerInformation($theLocalSource) ;
        $theObjects[] = new paypalIPNCurrencyAndExchangeInformation($theLocalSource) ;
        $theObjects[] = new paypalIPNDisputeNotification($theLocalSource) ;
        $theObjects[] = new paypalIPNMassPayment($theLocalSource) ;
        $theObjects[] = new paypalIPNPDTSpecific($theLocalSource) ;
        $theObjects[] = new paypalIPNSubscriptions($theLocalSource) ;
        $theObjects[] = new paypalIPNWebsiteAndRefundInformation($theLocalSource) ;

        /*
         * Start everything with the additional fields.  Fields of idential names from
         * the source will override these settings.
         */

        if (!is_null($anyAdditionalFields))
        {
            if (is_array($anyAdditionalFields))
            {
                $this->m_IPNData = $anyAdditionalFields ;
            }
        }

        /*
         * Now go through the objects and copy all the defined variables to this one.
         * All copying will be done by reference.
         */

        foreach ($theObjects as $theObject)
        {
            if ($theObject->m_dirty)
            {
                $theVariables = get_object_vars($theObject) ;

                foreach ($theVariables as $theVariableName => $theValue)
                {
                    if (substr($theVariableName, 0, 2) != "m_")
                    {
                        if (!is_null($theValue))
                        {
                            $this->m_IPNData[$theVariableName] =& $theObject->$theVariableName ;
                        }
                    }
                    else if ($theVariableName == "m_dynamicVariables")
                    {
                        $this->m_IPNData = array_merge($this->m_IPNData, $theObject->m_dynamicVariables) ;
                    }
                }
            }
        }
    }

    /**
     * @desc Add arbitrary IPN variable/values to the set collected already.
     * @param reference to associative array $theSource containing name/value pairs.
     * @return void
     * @access public
     */

    function addData(&$theSource)
    {
        if (is_array($theSource))
        {
            $this->m_IPNData = array_merge($this->m_IPNData, $theSource) ;
        }
    }

    /**
     * @desc fetch IPN data.
     * @return reference to the IPN data item.
     * @param string [optional] The name of the IPN data item.  If omitted a reference to m_IPNData is returned.
     * @access public
     */

    function &getData($theName = NULL)
    {
        if ($theName === NULL)
        {
            return $this->m_IPNData ;
        }
        else
        {
            if (isset($this->m_IPNData[$theName]))
            {
                return $this->m_IPNData[$theName] ;
            }
            else
            {
                $x = NULL ;
                return $x ;
            }
        }
    }

    /**
     * @desc Convert the variables in the data object to post string form.
     *
     * Apparently there is another "little" restriction with Paypal in that the
     * ORDER of the IPN fields as well as the VALUES must be preserved.
     *
     * @return string
     * @access public
     */

    function &asPostString ()
    {
        /*
         * It appears that Paypal insists that the data be posted back in the SAME ORDER
         * it was received.
         */

        $theKeys = array_keys($this->m_IPNData) ;
        $theKeys = array_flip($theKeys) ;

        $thePostString = "";

        foreach ($this->m_IPNDataOrder as $xxx => $theKey)
        {
            if (array_key_exists($theKey, $this->m_IPNData))
            {
                $thePostString = $thePostString . "&" . $theKey . '=' . urlencode($this->m_IPNData[$theKey]) ;
                unset($theKeys[$theKey]) ;
            }
        }

        /*
         * There MAY be extra stuff that needs to be sent back to Paypal (notably the cmd
         * field).  It all goes on the front of the post string and in the same order it
         * appears in the keys array (which explains the reverse).
         */

        $theKeys = array_reverse($theKeys) ;

        foreach ($theKeys as $theKey => $xxx)
        {
            $thePostString = "&" . $theKey . '=' . urlencode($this->m_IPNData[$theKey]) . $thePostString ;
        }

        /*
         * This is an undocumented change in the Paypal URL encoding.
         * The encoding results of the HEX value have to be in upper case.
         */

        preg_replace_callback('/%[\da-f]{2}/', create_function('$match', 'return strtoupper($match[0]) ;'), $thePostString) ;

        $thePostString = substr($thePostString, 1) ;
        return $thePostString ;
    }
}

?>
